import lm
import models
import logging
import sys
sys.path.append("python-graph/")
import graph
from google.appengine.ext import db
import datetime
#this is for SS2PL
def performop(optype,tid,din,div):	
	"""
		Returns none if lock cant be obtained
	"""
	trans = models.createbytid(tid)
	op = models.Operations(din=din,div=div,transaction=trans,optype=optype)

	if(lm.lock(op)):

		logging.info(("got lock for op: %s on di %s by transaction %d" % (optype,din,tid)))
		if(optype == "r" or optype == "w"):
			return op.perform()
		else:

			trans.endtime = datetime.datetime.now()
			if optype == "a":
				trans.isaborted=True
			trans.put()
		return True
	else:
		logging.info("Couldnt get lock")
		return None
		
		
def OCCSchedule (op, sno, din, div):
	otrans = models.OCCTrans.gql("WHERE sno = :1",sno).get()	
	if not otrans:
		otrans = models.OCCTrans.begin(sno)
	o = models.OCCOperations(din=din,div=div,optype=op,trans=otrans)
	if op == "r" or op == "w":
		return otrans.addop(o)
	elif op == "c":
		m1 = models.Mutex.gql("WHERE sid=:1","m1").get()
		if not m1:
			m1=models.Mutex(sid="m1")
			m1.put()
		m2 = models.Mutex.gql("WHERE sid=:1","m2").get()
		if not m2:
			m2=models.Mutex(sid="m2")
			m2.put()
		
		#val-write
		#optimization for read only transactions
		#should happen in critical section	
		retries = 3
		while retries > 0:
			if db.run_in_transaction(m1.lockup):
				break
			retries = retries-1
		if m1.lock:
			logging.info("accquired m1 lock")
			otrans.finishread()
			m1.unlock()
		else:
			return False
		
		#validate condition 2. the write set of any finished transactions shouldnt conflict with the read set of this transaction
		finishedtransactions = models.OCCTrans.gql("WHERE tid > :1 and tid <= :2",otrans.fstart,otrans.fend)
		valid = True;
		for ftrans in finishedtransactions:
			if models.isIntersecting(ftrans.ws,otrans.rs):
				valid = False
				otrans.invalidate()
				break
		if valid:
			for activetrans in models.OCCTrans.get(otrans.activeset):
				if models.isIntersecting(activetrans.ws,otrans.ws) or models.isIntersecting(activetrans.ws,otrans.rs):
					valid = False
					otrans.invalidate()
					break
		if valid:
			#needs to be in critical section
			retries = 3
			while retries > 0:
				if db.run_in_transaction(m2.lockup):
					break
				retries=retries-1
			if m2.lock:
				otrans.commit()
				models.OCCLog.addop(o)
			else:
				logging.error("couldnt get mutex lock")
				return False
		else:
			o.optype = "a"
			o.put()
			models.OCCLog.addop(o)
				
		return valid
	elif op == "a":
		otrans.isuseraborted = True
		otrans.invalidate()
		models.OCCLog.addop(o)
#this is for ss2pl			
def detectDeadlock():
	gr = graph.digraph()
	for waitingop in models.Operations.gql("WHERE iswaiting = :1",True):
		gr.add_node(waitingop.transaction.tid)
		lock = models.Locks.gql("WHERE din = :1",waitingop.din).get()
		if lock:
			for t in models.Transactions.get(lock.transactions):
				gr.add_node(t.tid)
				logging.info("Adding an edge between: %d %d" % (waitingop.transaction.tid,t.tid))
				gr.add_edge(waitingop.transaction.tid,t.tid)
		else:
			logging.error("No lock on data item: %s"%waitingop.din)
	returndict = {}

	returndict["dot"] = gr.write(fmt='dot')
	logging.info(returndict["dot"])
	cycle = gr.find_cycle()
	if cycle:
		logging.info("Cycle exists: %s" % ("".join(map(str,cycle))))
	else:
		logging.info("No cycle")
	
	returndict["cycle"] = cycle
	
	return returndict
	
def getcfg(stype="ss2pl"):
	logging.info("stype is :%s" % stype)
	gr = graph.digraph()
	iterablelist = []
	opsclass = None
	if stype == "ss2pl":
		iterablelist = models.Operations.gql("where optype IN :1 ",["r","w"])
		opsclass = models.Operations
	else:
		iterablelist = models.OCCOperations.gql("where optype IN :1 ",["r","w"])
		opsclass = models.OCCOperations
		
	for op in iterablelist:
		thistrans = None
		if stype == "ss2pl":
			thistransid = op.transaction.tid
		else:
			thistransid = op.trans.sno
		gr.add_node(thistransid)
		for dependentop in opsclass.gql("WHERE ca > :1 and din = :2 ORDER by ca",op.ca,op.din):
				#doing this check coz I can add that condition in the query as it requires me to have the order by thing as the first inequality but there can only be 2 inequalities. 
			dtrans = None
			if stype == "ss2pl":
				dtransid = dependentop.transaction.tid
			else:
				dtransid = dependentop.trans.sno
			if dtransid == thistransid:
				continue
			gr.add_node(dtransid)
			if op.optype == "r" and dependentop.optype == "r":
				continue
				#if dependentop.ca > op.ca:
			gr.add_edge(thistransid,dtransid)
	return {"dot":gr.write(fmt='dot')}
	
		
		#validate condition 3. the write set of any active transactions shouldnt conflict with read/write set of this transaction
	
